using System;
using System.Text;
using System.Collections;
using System.Data;
using System.Collections.Generic;
using System.Globalization;
using BLToolkit.Reflection;
using BLToolkit.Mapping;
using System.Reflection;

namespace CommonLibrary
{
	[System.Diagnostics.DebuggerDisplay("Buffer")]
	internal class JsonWriter
	{
		public StringBuilder Buffer{ get; set; }

		public JsonWriter ()
		{
			this.Buffer = new StringBuilder (256);
		}

		public void Write(object obj)
		{
			if (obj == null || obj == DBNull.Value) {
				this.Buffer.Append ("null");
				return;
			}

			var type = obj.GetType ();
			var code = type.IsGenericType && type.GetGenericTypeDefinition () == typeof(Nullable) ? 
				Type.GetTypeCode (type.GetGenericArguments () [0]) : 
				Type.GetTypeCode (type);

			Write (obj, type, code);
		}

		private void Write(Object obj, Type type, TypeCode code)
		{
			switch (code) {
			case TypeCode.Byte:
			case TypeCode.Int16:
			case TypeCode.Int32:
				if (type.IsEnum) {
					this.Buffer.Append ((int)obj);
					break;
				}

                goto case TypeCode.Int64;

			case TypeCode.Int64:

			case TypeCode.SByte:			
			case TypeCode.UInt16:
			case TypeCode.UInt32:
			case TypeCode.UInt64:

			case TypeCode.Decimal:
			case TypeCode.Single:
			case TypeCode.Double:
				this.WriteNumber (obj);
				break;

			case TypeCode.DateTime:
				this.WriteDateTime ((DateTime)obj);
				break;

			case TypeCode.String:
			case TypeCode.Char:
				WriteString (obj.ToString ());
				break;

			case TypeCode.Boolean:
				this.Buffer.Append ((bool)obj ? "true" : "false");
				break;

			case TypeCode.Object:
				this.WriteObject (obj, type);
				break;
			}
		}

		private void WriteNumber(object obj)
		{
			this.Buffer.Append (((IFormattable)obj).ToString (null, CultureInfo.InvariantCulture));
		}

		private void WriteSampleString(string obj)
		{
			this.Buffer.Append ('\"');
			this.Buffer.Append (obj);
			this.Buffer.Append ('\"');
		}

		private void WriteString(string s)
		{
			this.Buffer.Append ('\"');

			int runIndex = -1;
			int l = s.Length;
			for (var index = 0; index < l; ++index) {
				var c = s [index];

				if (c >= ' ' && c < 128 && c != '\"' && c != '\\') {
					if (runIndex == -1)
						runIndex = index;

					continue;
				}

				if (runIndex != -1) {
					this.Buffer.Append (s, runIndex, index - runIndex);
					runIndex = -1;
				}

				switch (c) {
				case '\t':
					this.Buffer.Append ("\\t");
					break;
				case '\r':
					this.Buffer.Append ("\\r");
					break;
				case '\n':
					this.Buffer.Append ("\\n");
					break;
				case '"':
				case '\\':
					this.Buffer.Append ('\\');
					this.Buffer.Append (c);
					break;
				default:
					this.Buffer.Append ("\\u");
					this.Buffer.Append (((int)c).ToString ("X4", NumberFormatInfo.InvariantInfo));
					break;
				}
			}

			if (runIndex != -1)
				this.Buffer.Append (s, runIndex, s.Length - runIndex);

			this.Buffer.Append ('\"');
		}

		private void WriteDateTime(DateTime dt)
		{
            this.Buffer.AppendFormat ("\"{0:yyyy-MM-ddTHH:mm:ssZ}\"", dt.ToUniversalTime ());
		}

        private void WriteObject(object obj, Type type)
        {
			if (type.IsArray) {
				WriteEnumerable ((IEnumerable)obj);
			} else if (obj is Hashtable) {
				WriteDictionary((IDictionary)obj);
			}
			else if (obj is IDictionary)
            {
                if (type.IsGenericType && type.GetGenericArguments()[0] == typeof(string))
                {
                    WriteDictionary((IDictionary)obj);
                }
                else
                {
                    WriteKeyValuePair((IDictionary)obj);
                }
            }
            else if (obj is IEnumerable)
			{
				WriteEnumerable((IEnumerable)obj);
            }
            else if (obj is DataTable)
            {
                var dt = (DataTable)obj;
                this.Buffer.Append('[');

                if (dt.Rows.Count > 0)
                {
                    #region WriteFirtstRow
                    this.Buffer.Append('{');
                    this.WriteSampleString(dt.Columns[0].ColumnName);
                    this.Buffer.Append(':');
                    this.Write(dt.Rows[0][0]);
                    for (int j = 1; j < dt.Columns.Count; ++j)
                    {
                        this.Buffer.Append(',');
                        this.WriteSampleString(dt.Columns[j].ColumnName);
                        this.Buffer.Append(':');
                        this.Write(dt.Rows[0][j]);
                    }

                    this.Buffer.Append('}');
                    #endregion

                    for (int i = 1; i < dt.Rows.Count; ++i)
                    {
                        this.Buffer.Append(",{");
                        this.WriteSampleString(dt.Columns[0].ColumnName);
                        this.Buffer.Append(':');
                        this.Write(dt.Rows[i][0]);
                        for (int j = 1; j < dt.Columns.Count; ++j)
                        {
                            this.Buffer.Append(',');
                            this.WriteSampleString(dt.Columns[j].ColumnName);
                            this.Buffer.Append(':');
                            this.Write(dt.Rows[i][j]);
                        }

                        this.Buffer.Append('}');
                    }
                }

                this.Buffer.Append(']');
            }
            else
            {
                // OBJECT
                TypeAccessor accessor = TypeAccessor.GetAccessor(type);
                this.Buffer.Append('{');
                MemberAccessor m;
                var e = accessor.GetEnumerator();
                if (e.MoveNext())
                {

                    m = (MemberAccessor)e.Current;
                    WriteSampleString(m.Name);
                    this.Buffer.Append(':');
                    Write(m.GetValue(obj));

                    while (e.MoveNext())
                    {
                        m = (MemberAccessor)e.Current;
                        this.Buffer.Append(',');
                        WriteSampleString(m.Name);
                        this.Buffer.Append(':');
                        Write(m.GetValue(obj));
                    }
                }

                this.Buffer.Append('}');
            }
        }

		private void WriteArray(IEnumerable args, Type type)
		{
			this.Buffer.Append ('[');
			var code = type.IsGenericType && type.GetGenericTypeDefinition () == typeof(Nullable) ? 
				Type.GetTypeCode (type.GetGenericArguments () [0]) : 
				Type.GetTypeCode (type);
			var enumerator = args.GetEnumerator ();

			if (enumerator.MoveNext ()) {
				this.Write (enumerator.Current, type, code);
				while (enumerator.MoveNext()) {
					this.Buffer.Append (',');
					this.Write (enumerator.Current, type, code);
				}
			}

			this.Buffer.Append (']');
		}

		private void WriteEnumerable(IEnumerable obj)
		{
			this.Buffer.Append ('[');
			var enumerator = obj.GetEnumerator ();

			if (enumerator.MoveNext ()) {
				this.Write (enumerator.Current);
				while (enumerator.MoveNext()) {
					this.Buffer.Append (',');
					this.Write (enumerator.Current);
				}
			}

			this.Buffer.Append (']');
		}

		/// <summary>
		/// {"Id":10, "Name":"HY"}
		/// </summary>
		/// <param name="obj">Object.</param>
		private void WriteDictionary(IDictionary obj)
		{
			this.Buffer.Append ('{');
			var e = obj.GetEnumerator ();
			if (e.MoveNext ()) {
				this.WriteSampleString ((string)e.Key);
				this.Buffer.Append (':');
				this.Write (e.Value);

				while (e.MoveNext()) {
					this.Buffer.Append (',');
					this.WriteSampleString ((string)e.Key);
					this.Buffer.Append (':');
					this.Write (e.Value);
				}
			}

			this.Buffer.Append ('}');
		}

		/// <summary>
		/// [{"Key":"Id", "Value": 10}, {"Key":"Name", "Value":"HY"}]
		/// </summary>
		/// <param name="obj">Object.</param>
		private void WriteKeyValuePair(IDictionary obj)
		{
			this.Buffer.Append ('[');
			var e = obj.GetEnumerator ();
			if (e.MoveNext ()) {
				this.Buffer.Append ("{\"Key\":");
				this.Write (e.Key);
				this.Buffer.Append (", \"Value\":");
				this.Write (e.Value);
				this.Buffer.Append ('}');

				while (e.MoveNext()) {
					this.Buffer.Append (",{\"Key\":");
					this.Write (e.Key);
					this.Buffer.Append (", \"Value\":");
					this.Write (e.Value);
					this.Buffer.Append ('}');
				}
			}

			this.Buffer.Append (']');
		}

		private void WriteDataTable(DataTable dt)
		{
		}
	}
}